import java.util.*;

public class CTransaction {
    private int nVersion; // 版本控制

    private List<CTxIn> vin; // 交易入流
    private List<CTxOut> vout; // 交易出流

    private int nLockTime; // 时间约定（初始版本中未用上）
    private List<CTxOut> vout1;

    public CTransaction() {
        nVersion = 1;
        vin = new ArrayList<>();
        vout = new ArrayList<>();
        nLockTime = 0;
    }

    // 判断交易是否为空（无入流、出流）
    public boolean isNull(){
        return vin.isEmpty() && vout.isEmpty();
    }
    
    // 计算hash
    public String getHash(){
        return Arrays.toString(Utils.serializeHash(this));
    }

    // 重写CTransaction的toString
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(String.format("CTransaction(hash=%s,ver=%d,vin.size=%d,vout.size=%d,nLockTime=%d\n",
                getHash().substring(0,6), nVersion, vin.size(), vout.size(), nLockTime));
        for (CTxIn txin : vin) {
            sb.append("    ").append(txin.toString()).append("\n");
        }
        for (CTxOut txout : vout) {
            sb.append("    ").append(txout.toString()).append("\n");
        }
        return sb.toString();
    }

    // 打印交易的字符串形式
    public void print(){
        System.out.println(toString());
    }

    // 从磁盘中读取交易数据
    public boolean readFromDisk(CDiskTxPos pos){
        // todo
        return true;
    }

    // 接受交易并将交易添加到内存
    public boolean acceptTransaction(CTxDB txdb, boolean flagCheckInputs, boolean[] flagsMissingInputs){
        if(flagsMissingInputs != null){
            flagsMissingInputs[0] = false;
        }
        if(isCoinBase()){
            return error("CTransaction:acceptTransaction() error: 创世交易作为单独的交易")
        }
        if(!checkTransaction()){
            return error("CTransaction:acceptTransaction() error: checkTransaction() failed")
        }
        uint256 hash = getHash();
        synchronized (CTransaction.class){
            if(mapTransactions.containsKey(hash)){
                return false;
            }
        }
        if(flagCheckInputs){
            if(txdb.containsTx(hash)){
                return false;
            }
        }

        // 检查内存中的冲突
        CTransaction ptxOld = null;
        for(int i = 0;i<vin.size();i++){
            COutPoint outpoint = vin.get(i).prevout;
            if(mapNextTx.constainsKey(outpoint)){
                // 将较新的交易替换同一交易
                if(i != 0)// ???
                {
                    return false;
                }
                ptxOld = mapNextTx.get(outpoint).ptx;
                if(!isNewerThan(ptxOld)){
                    return false;
                }
                for(int j = 0;j<vin.size();j++){
                    COutPoint point = vin.get(j).prevout;
                    if(!mapNextTx.containsKey(point)||mapNextTx.get(point).ptx != ptxOld){
                        return false;
                    }
                }
            }
            break;
        }

        // 检查先前交易
        Map<uint256,CTxIndex> mapUnused = new HashMap<uint256, CTxIndex>();
        long nFees = 0;
        if(flagCheckInputs && !connectInputs(txdb,mapUnused,new CDiskTxPos(1,1,1),0,nFees,false,false)){
            if(flagsMissingInputs != null){
                flagsMissingInputs[0] = true;
            }
            return error("CTransaction:acceptTransaction() error: 创世交易作为单独的交易"+hash.toString().substring(0,6));
        }

        // 将交易写入内存
        synchronized (CTransaction.class){
            if(ptxOld != null){
                System.out.println("mapTransaction.erase(" + ptxOld.getHash().toString() + ") replacing with new version");
                mapTransactions.remove(ptxOld.getHash());
            }
            addToMemoryPool();
        }

        // 若已经更新，则从钱包中删去旧的交易
        if(ptxOld != null){
            eraseFromWallet(ptxOld.getHash());
        }
        System.out.println("acceptTransaction(): accepted " + hash.toString().substring(0, 6));
        return true;
    }

    // 将交易添加到到内存池
    public boolean addToMemoryPool(){
        synchronized (CTransaction.class){
            uinit256 hash = geteHash();
            mapTransactions.put(hash,this);
            for(int i = 0;i < vin.size();i++){
                mapNextTx.put(vin.get(i).prevout,new CInPoint(this,i));
            }
            // nTransactionsUpdated++;
        }
        return true;
    }

    // 从内存池中移除交易
    public boolean removeFromMemoryPool(){
        synchronized (CTransaction.class){
            for(CTxIn txin : vin){
                mapNextTx.remove(txin.prevout);
            }
            mapTransactions.remove(getHash());
            // nTransactionsUpdated++;
        }
    }

    // 判断交易是否完成
    public boolean isFinal(int nBestHeight){
        if(nLockTime == 0||nLockTime < nBestHeight){
            return true;
        }
        for(CTxIn txin : vin){
            if(!txin.isFinal()){
                return false;
            }
        }
        return true;
    }

    // 判断当前交易是否比另一笔交易较新(有点像判断两笔交易是否相同: equals()）
    public boolean isNewerThan(CTransaction old){
        // 检查两笔交易的入流数量是否相同
        if(vin.size() < old.vin.size()){
            return false;
        }
        // 检查两笔交易的入流的前一个出流是否相同
        for(int i = 0;i<vin.size();i++){
            if (!vin.get(i).getPrevout().equals(old.vin.get(i).getPrevout())){
                return false;
            }
        }
        boolean flagNewer = false;
        int nLowest = Integer.MAX_VALUE;
        // 获取较新交易的序列号
        for(int i = 0;i < vin.size();i++){
            if(vin.get(i).getSequence() != old.vin.get(i).getSequence()){
                flagNewer = false;
                nLowest = vin.get(i).getSequence();
            }
            if(old.vin.get(i).getSequence() < nLowest){
                flagNewer = true;;
                nLowest = old.vin.get(i).getSequence();
            }
        }
        return flagNewer;
    }

    // 判断当前交易是否属于创世交易CoinBase
    public boolean isCoinBase(){
        return (vin.size() == 1 && vin.get(0).getPrevout().isNUll());
    }

    // 检查交易是否合法
    public boolean checkTransaction(){
        if(vin.isEmpty() && vout.isEmpty()){ // 交易的入流、出流均为空
            return false;
        }

        for(CTxOut txout : vout){
            if(txout.getValue() < 0){
                return false; // 存在交易的出流值为负数
            }
        }

        if(isCoinBase()){ // 若属于创世交易，检查该创世交易的公钥是否在【2，100】之间
            if (vin.get(0).getScriptSig().size() < 2 || vin.get(0).getScriptSig().size() > 100){
                return false;
            }
        }else { // 若不属于创世交易，检查入流的前一个交易的出流是否合法
            for(CTxIn txin : vin){
                if(txin.getPreout().isNull()){
                    return false;
                }
            }
        }

        return true;
    }

    // 判断当前交易是否属于我的钱包
    public boolean isMine(){
        for(CTxOut txout : vout){
            if(txout.isMine()){
                return true;
            }
        }
        return false;
    }

    // 计算交易出流的总Credit
    public long getCredit(){
        long nCredit = 0;
        for(CTxOut txout : vout){
            nCredit = nCredit + txout.getCredit();
        }
        return nCredit;
    }

    // 计算交易的出流值的总和 （注意区分Credit）
    public long getValueOut() throws RuntimeException{
        long nValueOut = 0;
        for(CTxOut txout : vout){
            if(txout.getValue() < 0){
                throw new RuntimeException("在CTransaction类的GetValue()中抛出异常：存在出流值为负数");
            }
            nValueOut = nValueOut + txout.getValue();
        }
        return nValueOut;
    }

    // 计算交易的最低手续费。其中flagDiscount为是否启用交易优惠规则
    public long getMinFee(boolean flagDiscount){
        int nBytes = getSerializeSize(); // 交易序列化后的大小？？？
        if(flagDiscount && nBytes < 10000){ // 启用交易优惠，且交易大小不超过10000
            return 0;
        }
        return (1+nBytes / 1000)*100;
    }

    // 返回交易序列化后的大小（固定值？？？） todo
    private int getSerializeSize() {
        return 1000;
    }

    // 判断两笔交易是否相等
    @Override
    public boolean equals(Object obj) {
        if (this == obj){
            return true;
        }
        if (obj == null || getClass() != obj.getClass()){
            return false;
        }
        CTransaction other = (CTransaction) obj;
        return nVersion == other.nVersion &&
                nLockTime == other.nLockTime &&
                vin.equals(other.vin) &&
                vout.equals(other.vout);
    }

    // 获取交易对象的hash值
    @Override
    public int hashCode() {
        int result = Integer.hashCode(nVersion);
        result = 31*result + vin.hashCode();
        result = 31*result + vout.hashCode();
        result = 31*result + Integer.hashCode(nLockTime);
        return result;
    }

    // 打印错误信息
    private boolean error(String msg){
        System.out.println(msg);
        return false;
    }
}

